package com.stone.rpc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <p>Created with IntelliJ IDEA. </p>
 * <p>User: Stony </p>
 * <p>Date: 2016/6/29 </p>
 * <p>Time: 16:23 </p>
 * <p>Version: 1.0 </p>
 */
public abstract class RpcFramework {
    private static final Logger logger = LoggerFactory.getLogger(RpcFramework.class);

    final static ExecutorService executorService = Executors.newFixedThreadPool(10);

    /**
     * 暴露服务
     * @param service 服务实现对象
     * @param port 接口
     * @throws Exception
     */
    public static void export(final Object service, int port) throws Exception{
        if (service == null)
            throw new IllegalArgumentException("service instance == null");
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        logger.info("Export service {} on port {}", service.getClass().getName(), port);
        ServerSocket serverSocket = new ServerSocket(port);
        while(true) {
            final Socket socket = serverSocket.accept();
            doExport(service, socket);
        }
    }

    /**
     * methodName,parameterTypes,arguments
     * @param service
     * @param socket
     */
    private static void doExport(final Object service, final Socket socket) {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    ObjectInputStream input = null;
                    ObjectOutputStream output = null;
                    try {
                        input = new ObjectInputStream(socket.getInputStream());
                        String methodName = input.readUTF();
                        Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                        Object[] arguments = (Object[]) input.readObject();

                        output = new ObjectOutputStream(socket.getOutputStream());
                        try {
                            Method method = service.getClass().getMethod(methodName, parameterTypes);
                            Object result = method.invoke(service, arguments);
                            output.writeObject(result);
                        } catch (Throwable t) {
                            output.writeObject(t);
                        }
                    } finally {
                        close(output);
                        close(input);
                        socket.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 引用服务
     * @param interfaceClass 接口类型
     * @param host 服务器主机名
     * @param port 服务器端口
     * @param <T> 接口泛型
     * @return 远程服务
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
        if (interfaceClass == null)
            throw new IllegalArgumentException("interFace class == null");
        if(!interfaceClass.isInterface()){
            throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
        }
        if(host == null || host.length() == 0){
            throw new IllegalArgumentException("The host must be not null.");
        }
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        logger.info("Get remote service {} from server {}:{}", interfaceClass.getName(), host, port);
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return doRefer(method, args, host, port);
            }
        });
    }

    /**
     * methodName,parameterTypes,arguments
     * @param method
     * @param args
     * @param host
     * @param port
     * @return
     * @throws IOException
     */
    private static Object doRefer(final Method method,final Object[] args ,final String host, final int port) throws Throwable {
        ObjectOutputStream output = null;
        ObjectInputStream input = null;
        Socket socket = null;
        try {
            socket = new Socket(host, port);
            output = new ObjectOutputStream(socket.getOutputStream());

            output.writeUTF(method.getName());
            output.writeObject(method.getParameterTypes());
            output.writeObject(args);

            input = new ObjectInputStream(socket.getInputStream());
            Object result = input.readObject();
            if (result instanceof Throwable) {
                throw (Throwable) result;
            }
            return result;
        } finally {
            close(input);
            close(output);
            close(socket);
        }
    }

    static void close(ObjectOutputStream output)throws IOException{
        if(null != output){
            output.close();
        }
    }
    static void close(ObjectInputStream input)throws IOException{
        if(null != input){
            input.close();
        }
    }

    static void close(Socket socket) throws IOException {
        if(null != socket){
            socket.close();
        }
    }

}
